#ifndef __ARRAY_HPP__
#define __ARRAY_HPP__

#include "math/math_config.h"
#include "util/allocator.hpp"
#include "util/exception.h"
#include "math/complex.hpp"
#include "math/tuple.hpp"
#include "math/vector.hpp"

#include "math/math_op.h"

NS_MATH_BEGIN

template<typename T, typename S = int32>
class array : xphysics::util::aligned_allocator<T, S>
{
public:
	typedef T value_type;
	typedef T& reference;
	typedef const T& const_reference;
	typedef S size_type;
	typedef array<T,S> this_type;

protected:
	// for 1D array
	value_type* m_data;
	size_type m_size;

	// for ND array
	vector<size_type> m_sizetable;

public:
	bool empty() const
	{
		return !m_data;
	}
	bool unempty() const
	{
		return m_data!=NULL;
	}
	void assign(const value_type& val)
	{
		
		for(size_type i=0;i<size();++i)
		{
			m_data[i] = val;
		}
	}

public:
	array():m_data(NULL),m_sizetable(NULL){}
	~array()
	{
		destroy(m_data);
	}
	array(const this_type& arr)
	{
		size_type sz = arr.size();
		m_data = create(sz);
		memcpy(m_data, arr.m_data, sizeof(value_type)*sz);
		m_sizetable = arr.m_sizetable;
	}
	array(this_type&& arr)
	{
		swap(arr);
	}

	array(size_type s0)
	{
		m_data = create(s0);
		m_sizetable.push_back(s0);
	}
	array(size_type s0, size_type s1)
	{
		m_data = create(s0*s1);
		m_sizetable.push_back(s0, s1);
	}
	array(size_type s0, size_type s1, size_type s2)
	{
		m_data = create(s0*s1*s2);
		m_sizetable.push_back(s0, s1, s2);
	}
	template<int N>
	array(const tuple<size_type, N>& sz)
	{
		size_type tot_size = 1;
		for(const size_type& s:sz)
		{
			tot_size *= s;
			m_sizetable.push_back(s);
		}
		m_data = create(tot_size);
	}

	array(const vector<size_type>& size_table):m_data(NULL),m_size(0),m_sizetable(size_table)
	{
		if(m_sizetable.empty())
		{
			return;
		}
		m_size = 1;
		for(size_t i=0;i<m_sizetable.size();++i)
		{
			m_size *= m_sizetable[i];
		}
		m_data = create(size());	
	}

public:
	this_type& operator= (const this_type& arr)
	{
		if(this==&arr)return *this;
		this_type tmp(arr);
		swap(tmp);
		return *this;
	}

	this_type& operator= (this_type&& arr)
	{
		swap(tmp);
		return *this;
	}


public:
	size_type size()const
	{
		if(!m_data)return 0;
		else
		{
			size_type sz = 1;
			for(const size_type& s:m_sizetable)
			{
				sz *= s;
			}
			return sz;
		}
	}

	const vector<size_type>& size_table()const
	{
		return this->m_sizetable();
	}

	size_type size(size_type k)const
	{
		if(!m_data)return 0;
		else
		{
			if(k<m_sizetable.size())return m_sizetable[k];
			else return 1;
		}
	}
	
public:
	void swap(this_type& arr)
	{
		m_sizetable.swap(arr.m_sizetable);
		util::xswap(m_data, arr.m_data);
	}

	reference operator[] (size_type i)
	{
		return m_data[i];
	}
	const_reference operator[] (size_type i) const
	{
		return m_data[i];
	}

	reference operator() (size_type i){return m_data[i];}
	const_reference operator() (size_type i) const{return m_data[i];}
	reference operator() (size_type i, size_type j){return m_data[m_sizetable[0]*j+i];}
	const_reference operator() (size_type i, size_type j)const{return m_data[m_sizetable[0]*j+i];}
	reference operator() (size_type i, size_type j, size_type k)
	{
		register size_type ind = i;
		register size_type st = m_sizetable[0];
		ind += st * j;
		st *= m_sizetable[1];
		ind += st * k;
		return m_data[ind];
	}
	const_reference operator() (size_type i, size_type j, size_type k) const
	{
		register size_type ind = i;
		register size_type st = m_sizetable[0];
		ind += st * j;
		st *= m_sizetable[1];
		ind += st * k;
		return m_data[ind];
	}

	template<int N>
	reference operator() (const tuple<size_type, N>& sub)
	{
		register size_type ind = 0;
		register size_type st = 1;
		for(register int i=0;i<N;++i)
		{
			ind += sub[i] * st;
			st *= m_sizetable[i];
		}
		return m_data[ind];
	}

	template<int N>
	const_reference operator() (const tuple<size_type, N>& sub) const
	{
		register size_type ind = 0;
		register size_type st = 1;
		for(register int i=0;i<N;++i)
		{
			ind += sub[i] * st;
			st *= m_sizetable[i];
		}
		return m_data[ind];
	}


public:
	// resize and reshape
	void resize(size_type s0)
	{
		size_type sz = size();
		if(s0==size())
		{
			m_sizetable.resize(1);
			m_sizetable[0] = s0;
		}else
		{
			this_type tmp(s0);
			if(s0>sz)
			{
				memcpy(tmp.m_data, m_data, sizeof(value_type)*sz);
			}else
			{
				memcpy(tmp.m_data, m_data, sizeof(value_type)*s0);
			}
			swap(tmp);
		}
	}

	void resize(size_type s0, size_type s1)
	{
		size_type sz = size();
		if(s0*s1==size())
		{
			m_sizetable.resize(2);
			m_sizetable[0] = s0;
			m_sizetable[1] = s1;
		}else
		{
			this_type tmp(s0, s1);
			if(s0*s1>sz)
			{
				memcpy(tmp.m_data, m_data, sizeof(value_type)*sz);
			}else
			{
				memcpy(tmp.m_data, m_data, sizeof(value_type)*(s0*s1));
			}
			swap(tmp);
		}
	}

	void resize(size_type s0, size_type s1, size_type s2)
	{
		size_type sz = size();
		size_type new_sz = s0*s1*s2;
		if(new_sz==size())
		{
			m_sizetable.resize(3);
			m_sizetable[0] = s0;
			m_sizetable[1] = s1;
			m_sizetable[2] = s2;
		}else
		{
			this_type tmp(s0, s1, s2);
			if(new_sz>sz)
			{
				memcpy(tmp.m_data, m_data, sizeof(value_type)*sz);
			}else
			{
				memcpy(tmp.m_data, m_data, sizeof(value_type)*new_sz);
			}
			swap(tmp);
		}
	}

	template<int N>
	void resize(const tuple<value_type, N>& sub)
	{
		if(sub.size()==0)
		{
			resize(0);
			return;
		}else if(sub.size()==1)
		{
			resize(sub[0]);
			return;
		}else if(sub.size()==2)
		{
			resize(sub[0], sub[1]);
			return;
		}else if(sub.size()==3)
		{
			resize(sub[0], sub[1], sub[2]);
			return;
		}else
		{
			size_type sz = size();
			size_type new_sz = 1;
			for(const size_type& val:sub)
			{
				new_sz *= val;
			}
			if(sz==new_sz)
			{
				m_sizetable.resize(N);
				for(int i=0;i<N;++i)
				{
					m_sizetable[i] = sub[i];
				}
			}else
			{
				this_type tmp(sub);
				if(new_sz>sz)
				{
					memcpy(tmp.m_data, m_data, sizeof(value_type)*sz);
				}else
				{
					memcpy(tmp.m_data, m_data, sizeof(value_type)*new_sz);
				}
				swap(tmp);
			}
		}
	}

	void resize(const vector<size_type>& size_table, const value_type& val)
	{
		swap(this_type(size_type, val));
	}

	template<typename XOP>
	void accept(XOP& op)
	{
		for(size_type ind=0;ind<size();++ind)
		{
			op.g(m_data[ind]);
		}
	}

	template<typename T2>
	bool size_match(const array<T2>& v)
	{
		if(v.m_sizetable.size()!=m_sizetable.size())
		{
			return false;
		}

		for(int i=0;i<sz_tbl.size();++i)
		{
			if(v.m_sizetable[i]!=m_sizetable[i])
			{
				return false;
			}
		}

		return true;

	}
	
public:
	// Operators
	operator bool () const
	{
		return m_data;
	}

	bool operator! () const
	{
		return !m_data;
	}

	OP1(+=);
	OP1(-=);
	OP1(*=);
	OP1(/=);

	OP2(+,+=);
	OP2(-,-=);
	OP2(*,*=);
	OP2(/,/=);
	
};

NS_MATH_END

#endif